package com.taotao.delayjob.core;


import com.taotao.delayjob.core.constant.RetryConstant;
import com.taotao.delayjob.core.constant.RetryResult;
import com.taotao.delayjob.core.constant.RetryStatus;
import com.taotao.delayjob.core.mapper.RetryInstMapper;
import com.taotao.delayjob.core.model.RetryInst;
import com.taotao.delayjob.core.model.RetryInstDto;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * <p>
 *
 * </p>
 *
 * @author fanbeibei
 * @version 1.0.0
 * @date 2024/7/17 14:31
 */
@Slf4j
@AllArgsConstructor
public class RetryTaskHelper {

    private RetryInstMapper retryInstMapper;
    private RetryTaskHub retryTaskHub;

    private ThreadPoolExecutor trggerThreadPool;

    /**
     * 提交重试任务
     *
     * @param dto
     */
    public void commitRetryTask(RetryInstDto dto) {
        RetryInst ri = new RetryInst();
        BeanUtils.copyProperties(dto, ri);
        ri.setNextTriggerTime(System.currentTimeMillis() + ri.getFirstDelay() * 60);
        ri.setStatus(RetryConstant.STATUS_NOT_SUCCESS);
        ri.setCreateTime(LocalDateTime.now());
        ri.setUpdateTime(LocalDateTime.now());
        retryInstMapper.batchInsert(Arrays.asList(ri));

    }

    /**
     * 重置重试次数
     */
    public void resetRetryNumToZero(Set<Long> retryInstIds) {
        Long nextTriggerTime = System.currentTimeMillis() + 1 * 60;
        retryInstMapper.resetRetryNum(retryInstIds, 0, nextTriggerTime, LocalDateTime.now());
    }


    public void triggerRetry(RetryInst r) {
        trggerThreadPool.execute(() -> {
            triggerRetry0(r);
        });
    }


    public void triggerRetry0(RetryInst r) {
        try {
            log.info(">>>>>>>>>>>> start trigger ,r={}", r);
            lockRetryInstByNextTriggerTime(r);
            IRetryHandler handler = retryTaskHub.getRetryHandler(r.getBusiCode());
            if (null == handler) {
                updateRetryFail(r, "未找到" + r.getBusiCode() + "对应的IRetryHandler");
                log.error("未找到{}对应的IRetryHandler,r={}", r.getBusiCode(), r);
                return;
            }

            RetryResult result = handler.retry(r);
            if (Objects.equals(result, RetryResult.SUCCESS)) {
                updateRetrySuccess(r);
            }

        } catch (Exception e) {
            updateRetryFail(r, e.getMessage());
            log.error("", e);
        } finally {
            log.info(">>>>>>>>>>>> end trigger ,r={}", r);
        }
    }

    private void lockRetryInstByNextTriggerTime(RetryInst r) {
        // 将下次触发时间暂时设置到5分钟后 ,防止重复触发
        Long nextTriggerTime = System.currentTimeMillis() + 5 * 60 * 1000;
        retryInstMapper.updateNextTriggerTime(r.getId(), nextTriggerTime, LocalDateTime.now());
    }


    public void rollback(RetryInst r) {
        trggerThreadPool.execute(() -> {
            rollback0(r);
        });
    }

    public void rollback0(RetryInst r) {
        try {
            log.info(">>>>>>>>>>>> start rollback ,r={}", r);
            IRetryHandler handler = retryTaskHub.getRetryHandler(r.getBusiCode());
            if (null == handler) {
                updateRollbackFail(r, "未找到" + r.getBusiCode() + "对应的IRetryHandler");
                log.error("未找到{}对应的IRetryHandler,r={}", r.getBusiCode(), r);
                return;
            }

            handler.rollback(r);
            updateRollbackSuccess(r);

        } catch (Exception e) {
            updateRetryFail(r, e.getMessage());
            log.error("", e);
        } finally {
            log.info(">>>>>>>>>>>> end rollback ,r={}", r);
        }
    }


    private void updateRetrySuccess(RetryInst r) {
        retryInstMapper.updateRetryResult(r.getId(), RetryStatus.success.getCode(), null, null, LocalDateTime.now());
    }

    private void updateRetryFail(RetryInst r, String reason) {
        Long nextTriggerTime = System.currentTimeMillis() + r.getIntervalSeconds() * 60;

        retryInstMapper.updateRetryResult(r.getId(), RetryStatus.not_success.getCode(), reason, nextTriggerTime, LocalDateTime.now());
    }

    public List<RetryInst> selectForRetryInst(long currentTimeMillis) {
        return retryInstMapper.selectForRetryInst(currentTimeMillis);
    }


    private void updateRollbackSuccess(RetryInst r) {
        retryInstMapper.updateRetryResult(r.getId(), RetryStatus.rollback.getCode(), null, null, LocalDateTime.now());
    }

    private void updateRollbackFail(RetryInst r, String reason) {
        Long nextTriggerTime = System.currentTimeMillis() + r.getIntervalSeconds() * 1000;

        retryInstMapper.updateRetryResult(r.getId(), RetryStatus.rollback_fail.getCode(), reason, nextTriggerTime, LocalDateTime.now());
    }
}
